/*
 * Hyuga: A Density-Grid Stream Clustering Platform.
 * Copyright (C) 2014 PUC-Rio/Laboratory for Advanced Collaboration
 *
 * Hyuga is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Hyuga is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Hyuga.  If not, see <http://www.gnu.org/licenses/>.
 */
package br.pucrio.inf.lac.konoha.hyuga.core;


public class Scenario {
    
    // Monitored Domain
    private double minlat;
    private double maxlat;
    private double minlng;
    private double maxlng;

    // DBSCAN Parameters
    private int    minPts;
    private double eps;
    private double win;
    
    // DG2CEP Parameters
    private boolean usingCellSubDiv;
    private String decayFunction;
    private int cellMinMinPts;
    private int cellSubDivision;
    
    private Scenario(ScenarioBuilder builder) {
        // Required Fields
        this.minlat  = builder.minlat;
        this.maxlat  = builder.maxlat;
        this.minlng  = builder.minlng;
        this.maxlng  = builder.maxlng;

        this.eps     = builder.eps;
        this.minPts  = builder.minPts;
        this.win     = builder.win;
        
        // Cell Subdivision?
        if (builder.usingCellSubDiv) {
            this.usingCellSubDiv = true;
            this.decayFunction   = builder.decayFunction;
            this.cellMinMinPts   = builder.cellMinMinPts;
            this.cellSubDivision = builder.cellSubDivision;
        } else {
            this.usingCellSubDiv = false;
        }
    }
    
    public static class ScenarioBuilder {
        // Monitored Domain
        private double minlat;
        private double maxlat;
        private double minlng;
        private double maxlng;
        
        // DBSCAN Parameters
        private double eps;
        private int    minPts;
        
        // DG2CEP Parameters
        private double win;
        
        // Non-Required Property
        private boolean usingCellSubDiv;
        private String decayFunction;
        private int cellMinMinPts;
        private int cellSubDivision;
        
        public ScenarioBuilder() {}
        
        public ScenarioBuilder latitude(double minlat, double maxlat) throws IllegalArgumentException {
            if (90  < Math.abs(minlat)) throw new IllegalArgumentException("Invalid latitude minimum value: | " + minlat + " | > 90");
            if (90  < Math.abs(maxlat)) throw new IllegalArgumentException("Invalid latitude maximum value: | " + maxlat + " | > 90");
            if (maxlat - minlat < 0)    throw new IllegalArgumentException("Invalid latitude range: [" + minlat + ", " + maxlat + "]");
            
            this.minlat = minlat;
            this.maxlat = maxlat;
            return this;
        }
        
        public ScenarioBuilder longitude(double minlng, double maxlng) throws IllegalArgumentException {            
            if (180 < Math.abs(minlng)) throw new IllegalArgumentException("Invalid longitude minimum value: | " + minlng + " | > 180");
            if (180 < Math.abs(maxlng)) throw new IllegalArgumentException("Invalid longitude maximum value: | " + maxlng + " | > 180");
            if (maxlng - minlng < 0)    throw new IllegalArgumentException("Invalid longitude range: [" + minlng + ", " + maxlng + "]");
            
            this.minlng = minlng;
            this.maxlng = maxlng;
            return this;
        }
        
        public ScenarioBuilder eps(double eps) throws IllegalArgumentException {
            if (0 >= eps) throw new IllegalArgumentException("Invalid eps value: " + eps + " < 0");
            
            this.eps = eps;
            return this;
        }
        
        public ScenarioBuilder minPts(int minPts) throws IllegalArgumentException {
            if (0 >= minPts) throw new IllegalArgumentException("Invalid minPts value: " + minPts + " < 0");
            
            this.minPts = minPts;
            return this;
        }
        
        public ScenarioBuilder timeWindow(double win) throws IllegalArgumentException {
            if (0 >= win) throw new IllegalArgumentException("Invalid time window value: " + win + " < 0");
            
            this.win = win;
            return this;
        }        
        
        public ScenarioBuilder usingCellSubDiv(boolean usingCellSubDiv) {
            this.usingCellSubDiv = usingCellSubDiv;
            
            return this;
        }
        
        public ScenarioBuilder cellSubDivision(int cellSubDivision) throws IllegalArgumentException {
            if (0 >= cellSubDivision) throw new IllegalArgumentException("Invalid cellsubdivision value: " + cellSubDivision + " < 0");
            this.cellSubDivision = cellSubDivision;
            
            return this;
        }
        
        public ScenarioBuilder cellMinMinPts(int minminPts) throws IllegalArgumentException {
            if (0 >= minminPts)      throw new IllegalArgumentException("Invalid minminPts value: " + minPts + " < 0");
            if (minminPts >= minPts) throw new IllegalArgumentException("Invalid minminPts value: " + minminPts + " > minPts = " + minPts);
            
            this.cellMinMinPts = minminPts;
            return this;
        }
        
        public ScenarioBuilder decayFunction(String decayFn) throws IllegalArgumentException {
            if (!decayFn.equalsIgnoreCase("linear") && !decayFn.equalsIgnoreCase("exponential"))
                throw new IllegalArgumentException("Unknown decay function: " + decayFn);
            
            this.decayFunction = decayFn;
            return this;
        }
        
        public Scenario build() {
            return new Scenario(this);
        }

    }

    //////////////////////////////////////////////////////////////////////////
    // Gets and Sets
    //////////////////////////////////////////////////////////////////////////

    public double getMinimumLatitude() {
        return minlat;
    }

    public double getMaximumLatitude() {
        return maxlat;
    }

    public double getMinimumLongitude() {
        return minlng;
    }
    
    public double getMaximumLongitude() {
        return maxlng;
    }

    public double getEps() {
        return eps;
    }

    public int getMinPts() {
        return minPts;
    }

    public double getTimeWindow() {
        return win;
    }
    
    public boolean isUsingCellSubDiv() {
        return usingCellSubDiv;
    }
    
    public int getCellMinMinPts() {
        return cellMinMinPts;
    }
    
    public int getCellSubDivision() {
        return cellSubDivision;
    }
    
    public String getDecayFunction() {
        return decayFunction;
    }
    
    public boolean hasDecayFunction() {
        return decayFunction != null && !decayFunction.equalsIgnoreCase("");
    }
    
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();

        sb.append("DEPLOYED CONFIG:")
          .append("\n\tLAT: [").append(minlat).append(", ").append(maxlat).append("], LNG: [").append(minlng).append(", ").append(maxlng).append("]")
          .append("\n\tEPS: ").append(eps).append(", MINPTS: ").append(minPts).append(", TIME: ").append(win)
          .append("\n\tUSING CELL SUBDIV:").append(usingCellSubDiv);
        
        if (usingCellSubDiv) {
            sb.append(", DECAY TYPE: ").append(decayFunction).append(", CELL MINMINPTS: ").append(cellMinMinPts)
              .append(", CELL SUBDIV SLOTS: ").append(cellSubDivision);
        }
        
        return sb.toString();
    }



}
